/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

//import nls = require('vs/nls');
//import * as perf from 'vs/base/common/performance';
import { TPromise } from 'vs/base/common/winjs.base';
import { WorkbenchShell } from 'vs/workbench/electron-browser/shell';
import * as browser from 'vs/base/browser/browser';
import { domContentLoaded } from 'vs/base/browser/dom';
//import errors = require('vs/base/common/errors');
import comparer = require('vs/base/common/comparers');
import platform = require('vs/base/common/platform');
//import paths = require('vs/base/common/paths');
import uri from 'vs/base/common/uri';
// import strings = require('vs/base/common/strings');
import { IWorkspaceContextService, Workspace, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
// import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
//import gracefulFs = require('graceful-fs');
import { IInitData } from 'vs/workbench/services/timer/common/timerService';
import { TimerService } from 'vs/workbench/services/timer/node/timerService';
//import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/keybindingService';
import { IWindowConfiguration, IWindowsService,	/*OpenContext*/ } from 'vs/platform/windows/common/windows';
import { WindowsService } from 'vs/platform/windows/electron-main/windowsService';
// import { WindowsChannelClient } from 'vs/platform/windows/common/windowsIpc';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { StorageService } from 'vs/platform/storage/common/storageService';
import { ChannelManager } from 'vs/base/parts/ipc/electron-browser/ipc.electron-browser';
import { IInstantiationService/*, ServicesAccessor*/ } from 'vs/platform/instantiation/common/instantiation';
// import { IUpdateService } from 'vs/platform/update/common/update';
//import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { WindowsManager } from 'vs/code/electron-main/windows';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { ILaunchService, LaunchService, LaunchChannel } from 'vs/code/electron-main/launch';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
// import { UpdateChannel/*, UpdateChannelClient*/ } from 'vs/platform/update/common/updateIpc';
import { WindowsChannelClient, WindowsChannel } from 'vs/platform/windows/common/windowsIpc';
import { IURLService } from 'vs/platform/url/common/url';
import { URLService } from 'vs/platform/url/electron-main/urlService';
import { URLChannel, URLChannelClient } from 'vs/platform/url/common/urlIpc';
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
import { WorkspacesChannel, WorkspacesChannelClient } from 'vs/platform/workspaces/common/workspacesIpc';
import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
// import { UpdateChannelClient } from 'vs/platform/update/common/updateIpc';
// import { IUpdateService } from 'vs/platform/update/common/update';
// import { IURLService } from 'vs/platform/url/common/url';
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
// import { createSpdLogService } from 'vs/platform/log/node/spdlogService';

import { ConsoleLogService, MultiplexLogService, ILogService, ConsoleLogMainService, DEFAULT_LOG_LEVEL } from 'vs/platform/log/common/log';
// import { IssueChannelClient } from 'vs/platform/issue/common/issueIpc';
// import { IIssueService } from 'vs/platform/issue/common/issue';
import { LogLevelSetterChannelClient, FollowerLogService, LogLevelSetterChannel } from 'vs/platform/log/common/logIpc';
//import { LogLevel } from 'vs/workbench/api/node/extHostTypes';
import { HistoryMainService } from 'vs/platform/history/electron-main/historyMainService';
import { IHistoryMainService } from 'vs/platform/history/common/history';
import { ILifecycleService, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { IStateService } from 'vs/platform/state/common/state';
import { StateService } from 'vs/platform/state/node/stateService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { IRequestService } from 'vs/platform/request/node/request';
import { RequestService } from 'vs/platform/request/electron-main/requestService';
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
import { IBackupMainService } from 'vs/platform/backup/common/backup';
import { IChoiceService } from 'vs/platform/message/common/message';
import { ChoiceCliService } from 'vs/platform/message/node/messageCli';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { Hub } from '../../base/common/hub';
import { StandaloneThemeService } from 'vs/editor/standalone/browser/standaloneThemeService';
import { IStandaloneThemeService } from 'vs/editor/common/standalone/standaloneThemeService';
// gracefulFs.gracefulify(fs); // enable gracefulFs
export var shell: WorkbenchShell;
export var editingFolderPath: string;
export function startup(configuration: IWindowConfiguration): TPromise<void> {
	return Hub.connect().then(() => {
		return Hub.server.call("init").done((r: any) => {
			/*'aix'
                  | 'android'
                  | 'darwin'
                  | 'freebsd'
                  | 'linux'
                  | 'openbsd'
                  | 'sunos'
				  | 'win32'; */
			platform.sep = r.sep;
			window["files"] = r.files;
			switch (r.platform) {
				case 'linux':
					window["isLinuxServer"] = true;
					window["isWindowsServer"] = false;
					platform.isLinuxServer = true;
					platform.isWindowsServer = false;
					break;
				case 'win32':
					window["isLinuxServer"] = false;
					window["isWindowsServer"] = true;
					platform.isWindowsServer = true;
					platform.isLinuxServer = false;
					break;
			}
		});
	}).then(() => {
		// Ensure others can listen to zoom level changes
		//browser.setZoomFactor(webFrame.getZoomFactor());

		// See https://github.com/Microsoft/vscode/issues/26151
		// Can be trusted because we are not setting it ourselves.
		//browser.setZoomLevel(webFrame.getZoomLevel(), true /* isTrusted */);

		configuration.windowId = 1;
		browser.setFullscreen(!!configuration.fullscreen);

		//KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged();

		browser.setAccessibilitySupport(configuration.accessibilitySupport ? platform.AccessibilitySupport.Enabled : platform.AccessibilitySupport.Disabled);

		// Setup Intl
		comparer.setFileNameComparer(new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }));

		// Open workbench
		return openWorkbench(configuration);
	});
}

function openWorkbench(configuration: IWindowConfiguration): TPromise<void> {
	var mainServices = createMainProcessServices(configuration);
	const environmentService = new EnvironmentService(configuration, "");// <IEnvironmentService>mainServices.get(IEnvironmentService);// new EnvironmentService(configuration, "");
	const logService = createLogService(configuration, environmentService);
	logService.trace('openWorkbench configuration', JSON.stringify(configuration));
	// Since the configuration service is one of the core services that is used in so many places, we initialize it
	// right before startup of the workbench shell to have its data ready for consumers
	return createAndInitializeWorkspaceService(configuration, <EnvironmentService>environmentService).then(workspaceService => {
		//const timerService = new TimerService((<any>window).MonacoEnvironment.timers as IInitData);
		const timerService = new TimerService({} as any as IInitData);
		const storageService = createStorageService(workspaceService, environmentService);

		return domContentLoaded().then(() => {

			// Open Shell
			shell = new WorkbenchShell(document.body, {
				contextService: workspaceService,
				configurationService: workspaceService,
				environmentService,
				logService,
				timerService,
				storageService
			}, mainServices, configuration);
			shell.open();

			// Inform user about loading issues from the loader
			// (<any>self).require.config({
			// 	onError: (err: any) => {
			// 		if (err.errorCode === 'load') {
			// 			shell.onUnexpectedError(loaderError(err));
			// 		}
			// 	}
			// });
		});
	});
}

function createAndInitializeWorkspaceService(configuration: IWindowConfiguration, environmentService: EnvironmentService): TPromise<WorkspaceService> {
	const workspaceService = new WorkspaceService();
	editingFolderPath = /*configuration.workspace || */configuration.folderPath;// || configuration;
	return workspaceService.initialize(editingFolderPath).then(() => workspaceService, error => workspaceService);
}

function createStorageService(workspaceService: IWorkspaceContextService, environmentService: IEnvironmentService): IStorageService {
	let workspaceId: string;
	let secondaryWorkspaceId: number;

	switch (workspaceService.getWorkbenchState()) {

		// in multi root workspace mode we use the provided ID as key for workspace storage
		case WorkbenchState.WORKSPACE:
			workspaceId = uri.from({ path: workspaceService.getWorkspace().id, scheme: 'root' }).toString();
			break;

		// in single folder mode we use the path of the opened folder as key for workspace storage
		// the ctime is used as secondary workspace id to clean up stale UI state if necessary
		case WorkbenchState.FOLDER:
			const workspace: Workspace = <Workspace>workspaceService.getWorkspace();
			workspaceId = workspace.folders[0].uri.toString();
			secondaryWorkspaceId = workspace.ctime;
			break;

		// finaly, if we do not have a workspace open, we need to find another identifier for the window to store
		// workspace UI state. if we have a backup path in the configuration we can use that because this
		// will be a unique identifier per window that is stable between restarts as long as there are
		// dirty files in the workspace.
		// We use basename() to produce a short identifier, we do not need the full path. We use a custom
		// scheme so that we can later distinguish these identifiers from the workspace one.
		case WorkbenchState.EMPTY:
			workspaceId = workspaceService.getWorkspace().id;
			break;
	}
	var storage = window.localStorage;

	return new StorageService(storage, storage, workspaceId, secondaryWorkspaceId);
}

function createLogService(configuration: IWindowConfiguration, environmentService: IEnvironmentService): ILogService {
	const consoleLogService = new ConsoleLogService(configuration.logLevel);
	const logService = new MultiplexLogService([consoleLogService]);
	const logLevelClient = new LogLevelSetterChannelClient(ChannelManager.getChannel('loglevel'));
	return new FollowerLogService(logLevelClient, logService);
}
// function getLogLevel(environmentService: IEnvironmentService): LogLevel {
// 	if (environmentService.verbose) {
// 		return LogLevel.Trace;
// 	}
// 	if (typeof environmentService.args.log === 'string') {
// 		const logLevel = environmentService.args.log.toLowerCase();
// 		switch (logLevel) {
// 			case 'trace':
// 				return LogLevel.Trace;
// 			case 'debug':
// 				return LogLevel.Debug;
// 			case 'info':
// 				return LogLevel.Info;
// 			case 'warn':
// 				return LogLevel.Warning;
// 			case 'error':
// 				return LogLevel.Error;
// 			case 'critical':
// 				return LogLevel.Critical;
// 			case 'off':
// 				return LogLevel.Off;
// 		}
// 	}
// 	return DEFAULT_LOG_LEVEL;
// }
function parseURLQueryArgs() {
	const search = window.location.search || '';

	return search.split(/[?&]/)
		.filter(function (param) { return !!param; })
		.map(function (param) { return param.split('='); })
		.filter(function (param) { return param.length === 2; })
		.reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {});
}
function initFirstServices(): IInstantiationService {
	const services = new ServiceCollection();

	const args = parseURLQueryArgs();
	const configuration = JSON.parse(args['config'] || '{}') || {};
	const environmentService = new EnvironmentService(configuration, "");
	const consoleLogService = new ConsoleLogMainService(DEFAULT_LOG_LEVEL);
	services.set(IEnvironmentService, environmentService);
	services.set(ILogService, consoleLogService);
	services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
	services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
	services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
	services.set(IStateService, new SyncDescriptor(StateService));
	services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
	services.set(IRequestService, new SyncDescriptor(RequestService));
	services.set(IURLService, new SyncDescriptor(URLService, []));
	services.set(IBackupMainService, new SyncDescriptor(BackupMainService));
	services.set(IChoiceService, new SyncDescriptor(ChoiceCliService));
	services.set(ITelemetryService, NullTelemetryService);
	services.set(IStandaloneThemeService, new SyncDescriptor(StandaloneThemeService));
	return new InstantiationService(services, true);
}

function initSecondServices(): IInstantiationService {
	const services = new ServiceCollection();

	services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, 0));
	services.set(IWindowsService, new SyncDescriptor(WindowsService));
	services.set(ILaunchService, new SyncDescriptor(LaunchService));

	return initFirstServices().createChild(services);
}


function registerChannel(): void {
	initSecondServices().invokeFunction(accessor => {
		const appInstantiationService = accessor.get(IInstantiationService);

		const launchService = accessor.get(ILaunchService);
		const launchChannel = new LaunchChannel(launchService);
		ChannelManager.registerChannel('launch', launchChannel);

		// // Register more Electron IPC services
		// const updateService = accessor.get(IUpdateService);
		// const updateChannel = new UpdateChannel(updateService);
		// ChannelManager.registerChannel('update', updateChannel);

		const urlService = accessor.get(IURLService);
		const urlChannel = appInstantiationService.createInstance(URLChannel, urlService);
		ChannelManager.registerChannel('url', urlChannel);

		const workspacesService = accessor.get(IWorkspacesMainService);
		const workspacesChannel = appInstantiationService.createInstance(WorkspacesChannel, workspacesService);
		ChannelManager.registerChannel('workspaces', workspacesChannel);

		const windowsService = accessor.get(IWindowsService);
		const windowsChannel = new WindowsChannel(windowsService);
		ChannelManager.registerChannel('windows', windowsChannel);

		// Log level management
		const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService));
		ChannelManager.registerChannel('loglevel', logLevelChannel);

	});
}

// function initServices(): IInstantiationService {
// 	const services = new ServiceCollection();
// 	services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
// 	services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, "0"));
// 	services.set(IWindowsService, new SyncDescriptor(WindowsService));
// 	services.set(ILaunchService, new SyncDescriptor(LaunchService));
// 	services.set(IURLService, new SyncDescriptor(URLService, []));
// 	services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
// 	//services.set(IIssueService, new SyncDescriptor(IssueService, machineId));
// 	return new InstantiationService(services, true)
// }
function createMainProcessServices(configuration: IWindowConfiguration): ServiceCollection {
	registerChannel();
	const serviceCollection = new ServiceCollection();

	const windowsChannel = ChannelManager.getChannel('windows');
	serviceCollection.set(IWindowsService, new WindowsChannelClient(windowsChannel));

	// const updateChannel = ChannelManager.getChannel('update');
	// serviceCollection.set(IUpdateService, new SyncDescriptor(UpdateChannelClient, updateChannel));

	const urlChannel = ChannelManager.getChannel('url');
	serviceCollection.set(IURLService, new SyncDescriptor(URLChannelClient, urlChannel, 0));

	const workspacesChannel = ChannelManager.getChannel('workspaces');
	serviceCollection.set(IWorkspacesService, new WorkspacesChannelClient(workspacesChannel));

	return serviceCollection;
}

// function loaderError(err: Error): Error {
// 	if (platform.isWeb) {
// 		return new Error(nls.localize('loaderError', "Failed to load a required file. Either you are no longer connected to the internet or the server you are connected to is offline. Please refresh the browser to try again."));
// 	}

// 	return new Error(nls.localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err)));
// }
